
from app import create_app
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from fastapi import Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse,JSONResponse
from app.plugins.CheckJWT import verify_token,create_token
import asyncio
from redis.asyncio import Redis
import subprocess
import logging
from logging.handlers import RotatingFileHandler
import time
import json
import os
import sys



app = create_app()
BASE_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
template_PATH = os.path.join(BASE_DIR, 'app/templates')
template = Jinja2Templates(directory=template_PATH)
@app.exception_handler(404)
async def not_found_handler(request: Request, error):
    return template.TemplateResponse("404.html", {"request": request})



def setup_logging():
    log_format = "%(asctime)s - %(levelname)s - %(name)s - URL: %(url)s - %(message)s"
    formatter = logging.Formatter(log_format)
    
    # 清除现有处理器，避免重复添加
    logger = logging.getLogger()
    if logger.hasHandlers():
        logger.handlers = []
    
    logger.setLevel(logging.INFO)
    
    BASE_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
    applog_path = os.path.join(BASE_DIR, 'app.log')
    # app.log 处理器（仅 INFO~WARNING）
    file_handler = RotatingFileHandler(
        applog_path, maxBytes=1024*1024, backupCount=5, encoding="utf-8"
    )
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)
    # 添加过滤器：仅处理 INFO~WARNING（避免与 errors.log 重复）
    class InfoFilter(logging.Filter):
        def filter(self, record):
            return record.levelno < logging.ERROR
    file_handler.addFilter(InfoFilter())
    logger.addHandler(file_handler)
    # 此处加过滤器：拦截不记录特定URL日志
    class UrlExcludeFilter(logging.Filter):
        def filter(self, record):
            url = getattr(record, "url", None)
            if url and "/topology/NodesStatus" in url:
                return False
            return True
    file_handler.addFilter(UrlExcludeFilter())
    logger.addHandler(file_handler)
    
    errors_log_path = os.path.join(BASE_DIR, 'errors.log')
    # errors.log 处理器（仅 ERROR 及以上）
    error_handler = RotatingFileHandler(
        errors_log_path, maxBytes=1024*1024, backupCount=5, encoding="utf-8"
    )
    error_handler.setFormatter(formatter)
    error_handler.setLevel(logging.ERROR)
    logger.addHandler(error_handler)

# 初始化日志
if not logging.getLogger().hasHandlers():
    setup_logging()
logger = logging.getLogger(__name__)

# logger.propagate = False  # 禁用传播（可选，根据需求）

#token中间件
@app.middleware("http")
async def check_token(request: Request, call_next):
    url_prefix = ['/login','/login/publickey','/logincheck','/', '/register','/register/checkUserName','/register/getPublicKey','/register/toRegister']
    static_prefix = ['/static','/qcow2','/config']
    path = request.url.path
    if path in url_prefix or any(path.startswith(prefix) for prefix in static_prefix):
        response= await call_next(request)
        return response
    else:
        try:
            token = request.cookies['Authorization']
            res = verify_token(token)
        except Exception as e:
            if "x-requested-with" in request.headers:
                return JSONResponse({
                    "status": "redirect",
                    "redirect_url": "/login"
                },status_code=302)
            else:
                # 对于普通表单提交，直接返回302重定向
                return RedirectResponse(url="/login", status_code=302)
        if res:
            request.state.user = res#将用户名存在request.state.user中
            response = await call_next(request)
            #刷新token
            token = create_token({"username":res})
            response.set_cookie(key="Authorization",value=token,path="/")
            return response
        else:
            if "x-requested-with" in request.headers:
                return JSONResponse({
                    "status": "redirect",
                    "redirect_url": "/login"
                },status_code=302)
            else:
                # 对于普通表单提交，直接返回302重定向
                return RedirectResponse(url="/login", status_code=302)


# 自定义中间件 - 记录所有访问的URL和处理时间
@app.middleware("http")
async def log_requests(request: Request, call_next):
    # 记录请求开始时间
    start_time = time.time()
    
    # 提取请求信息
    client_host = request.client.host
    method = request.method
    url = str(request.url)
    
    try:
        # 处理请求
        response = await call_next(request)
        
        # 计算处理时间
        process_time = time.time() - start_time
        
        # 记录访问日志，添加URL信息到日志记录中
        extra = {"url": url}
        logger.info(
            f"客户端: {client_host}, 方法: {method}, 状态码: {response.status_code}, "
            f"处理时间: {process_time:.2f}秒",
            extra=extra
        )
        
        return response
        
    except Exception as e:
        # 记录错误日志
        process_time = time.time() - start_time
        extra = {"url": url}
        logger.error(
            f"客户端: {client_host}, 方法: {method}, 错误: {str(e)}, "
            f"处理时间: {process_time:.2f}秒",
            exc_info=True,  # 记录完整堆栈信息
            extra=extra
        )
        
        # 返回友好的错误响应
        return JSONResponse(
            status_code=500,
            content={"message": "服务器内部错误，请稍后再试"}
        )





app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

def shell_cmd(command):
        # 构造管道命令（注意：awk的单引号需转义，或用双引号包裹整个命令）
    try:
        # 执行命令：shell=True让shell处理管道，text=True让输出为字符串
        result = subprocess.run(
            command,
            shell=True,
            stdout=subprocess.PIPE,  # 捕获标准输出（状态）
            stderr=subprocess.PIPE,  # 捕获标准错误（错误信息）
            text=True,               # 输出为字符串（而非字节）
            timeout=10               # 超时时间（避免命令挂起）
        )
        if result.returncode == 0:
            status = result.stdout.strip()
            return status
        else:
            # 命令执行失败（如qm出错）
            error_msg = result.stderr.strip()
            return f"命令执行失败：{error_msg}"
    except subprocess.TimeoutExpired:
        return "错误：命令执行超时"
    except Exception as e:
        return f"未知错误：{str(e)}"
#检查设备运行状态，更新redis,检测是否有临时网卡，没有需要创建，检测是否有vmbr0-9，没有需要创建
async def check_device_status():
    #检测是否安装了redis
    cmd = 'systemctl status redis-server | grep "active (running)"'
    result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=10)
    if result.stderr:
        cmd = "apt-get install redis-server -y"
        result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        cmd = 'systemctl status redis-server | grep "active (running)"'
        result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=10)
        if result.stdout=='':
            raise Exception("redis安装失败")
    if result.stdout=='':
        cmd = "apt-get install redis-server -y"
        result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        cmd = 'systemctl status redis-server | grep "active (running)"'
        result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=10)
        if result.stdout=='':
            raise Exception("redis安装失败")
    cmd = "brctl show | grep vmbr | awk '{print $1}'"
    result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=10)
    interfaces = result.stdout.strip().split('\n')
    #检测是否有vmbr0-9，没有需要创建
    for i in range(0,10):
        if f"vmbr{i}" not in interfaces:
            #创建vmbr0-9
            create_bridge_step1 = "sed -i '$d' /etc/network/interfaces"
            shell_cmd(create_bridge_step1)
            create_bridge_step2  = f'''printf "auto vmbr{i}\niface vmbr{i} inet static\n        bridge-ports none\n        bridge-stp off\n        bridge-fd 0\nsource /etc/network/interfaces.d/*\n" >> /etc/network/interfaces'''
            shell_cmd(create_bridge_step2)
            #创建临时网桥
            create_bridge_step3 =f'brctl addbr vmbr{i}'
            shell_cmd(create_bridge_step3)
            create_bridge_step4 =f'ip link set vmbr{i} up'
            shell_cmd(create_bridge_step4)
    #检测是否有临时网卡，没有需要创建
    cmd = "brctl show tmpbr0"
    result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=10)
    if result.stderr:
        #创建临时网桥
        create_bridge_step1 = "sed -i '$d' /etc/network/interfaces"
        shell_cmd(create_bridge_step1)
        create_bridge_step2  = f'''printf "auto tmpbr0\niface tmpbr0 inet static\n        bridge-ports none\n        bridge-stp off\n        bridge-fd 0\nsource /etc/network/interfaces.d/*\n" >> /etc/network/interfaces'''
        shell_cmd(create_bridge_step2)
        create_bridge_step3 =f'brctl addbr tmpbr0'
        shell_cmd(create_bridge_step3)
        create_bridge_step4 =f'ip link set tmpbr0 up'
        shell_cmd(create_bridge_step4)
    result = subprocess.run(
                'qm list',              # 要执行的命令
                shell=True,               # 使用shell执行命令
                stdout=subprocess.PIPE,   # 获取命令输出
                stderr=subprocess.PIPE,   # 获取命令错误输出
                text=True,               # 输出为字符串（而非字节）
                timeout=10               # 超时时间（避免命令挂起）
    )
    data_str = result.stdout.strip()
    lines = [line.strip() for line in data_str.split('\n') if line.strip()]
    result = []#[{'vmid': 101, 'name': '128T-1', 'status': 'stopped'}, {'vmid': 20000, 'name': 'cs0-test-cpe00', 'status': 'stopped'}]
    for line in lines[1:]:  # 从第二行开始处理
        parts = [part for part in line.split() if part]
        vmid = int(parts[0])
        name = parts[1]
        status = parts[2]
        result.append({
            "vmid": vmid,
            "name": name,
            "status": status
        })
    async with Redis.from_url(url="redis://localhost:6379/0",decode_responses=True) as redis:
        #检查是否有userinfo表
        userinfo = await redis.smembers('userinfo')
        if not userinfo:
            #没有userinfo表，创建userinfo表
            await redis.sadd('userinfo',json.dumps({"name":"admin","password":"634484b8c7d777add5e6aa99ab246c6f7b7c9f41"}))
        #获取所有key
        keys = await redis.keys('*')
        #过滤key包含allnodes的key
        keys = [key for key in keys if 'allnodes' in key]
        for i in keys:
            #获取key对应的值
            value  =json.loads(await redis.hget(i,"allnodes"))
            for j in value:
                for m in result:
                    if m["vmid"]==j["properties"]["zIndex"]:
                        if m["status"] == "running":
                            j["properties"]["status"] = ""
                        else:
                            j["properties"]["status"] = "down"
            #更新redis
            await redis.hset(i,"allnodes",json.dumps(value))
    
if __name__ == '__main__':
    asyncio.run(check_device_status())
    
    uvicorn.run(app=app, host='0.0.0.0',access_log=False, port=80,reload=False)